
gridder = {
num: 0,		// the selected number that will be inserted on click
hl_row: -1,	// the highlit row
hl_col: -1,	// the highlit column
// flags:
autoclean_hint: 1,	// automatically clean the hints when a number was placed
autocheck:	1,	// automatically check the integrity after each number placed
autochecknum:	1,	// automatically mark completed numbers
col_css_rules:	[ null, null, null, null, null, null, null, null, null ],
grid: 		null,
key: function(ev) {	// the 0-9 keys handler
	if (ev.keyCode>=48 && ev.keyCode<=57 || 32==ev.keyCode || 88==ev.keyCode || 120==ev.keyCode) {
		var k = ev.keyCode-48;
		if (32==ev.keyCode || 88==ev.keyCode || 120==ev.keyCode) {
			k = 0;
		}
		gridder.set_num(k);
	}
},
set_num: function(n) {
	var sp = document.getElementById("s" + gridder.num);
	if (-1!=sp.className.indexOf("usedup")) {
		sp.className = "usedup";
	} else {
		sp.className = "";
	}
	gridder.num = n;
	sp = document.getElementById("s" + gridder.num);
	sp.className = "selmark " + sp.className;

	for (var r = 0; r<9; r++) {
		for (var c = 0; c<9; c++) {
			var el = document.getElementById("mn" + r + c);
			if (gridder.num==gridder.cache[r][c] || gridder.num==-gridder.cache[r][c]) {
				if (0!=gridder.num) {
					el.className+= " selmark";
				}
			} else {
				if (gridder.cache[r][c]<0) {
					el.className = "mn hard";
				} else {
					el.className = "mn";
				}
			}
		}
	}
},
init: function() {
	Components.utils.import("resource://sudoku/griddb.jsm");

	var prefService = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
	gridder.prefs = prefService.getBranch("extensions.sudoku.");

        gridder.strings = Components.classes["@mozilla.org/intl/stringbundle;1"].getService().QueryInterface(Components.interfaces.nsIStringBundleService).createBundle("chrome://sudoku/locale/sudoku.properties");
	document.getElementById("completed").innerHTML = gridder.strings.GetStringFromName("congratulations.label");


	var ths = document.getElementsByTagName('th');
	for (var i = 0;i<ths.length;i++) {
		ths[i].addEventListener("mouseup", gridder.cell_click, false);
		ths[i].addEventListener("dblclick", gridder.cell_dblclick, false);
		ths[i].addEventListener("mouseover", gridder.cell_enter, false);
		ths[i].addEventListener("mouseout", gridder.cell_leave, false);
	}
	gridder.find_col_css();
	gridder.cache = [
		[ 0,0,0,0,0,0,0,0,0 ],
		[ 0,0,0,0,0,0,0,0,0 ],
		[ 0,0,0,0,0,0,0,0,0 ],
		[ 0,0,0,0,0,0,0,0,0 ],
		[ 0,0,0,0,0,0,0,0,0 ],
		[ 0,0,0,0,0,0,0,0,0 ],
		[ 0,0,0,0,0,0,0,0,0 ],
		[ 0,0,0,0,0,0,0,0,0 ],
		[ 0,0,0,0,0,0,0,0,0 ]
	];
	gridder.ticks = -1;
	gridder.timer_on = 0;
	gridder.timer = setInterval(gridder.watch, 1000);

	var p = window.location.href.indexOf("grid.html?id=");
	if (-1!=p) {
		var tail = window.location.href.substr(p+13);
		var g = griddb.get_grid_by_id(1*tail);
		gridder.set_grid(g);
		return ;
	}
	p = window.location.href.indexOf("grid.html?p=");
	if (-1!=p) {
		var tail = window.location.href.substr(p+12);
		// straight puzzle
		gridder.introduce(tail, null);
	} else {
		gridder.random_grid();
	}
},

random_grid: function() {
	document.getElementById("completed").style.visibility = "hidden";
	gridder.set_grid(griddb.get_random_grid());
},
introduce: function(tail, gid) {
	var g = griddb.introduce(tail, gid);
	gridder.set_grid(g);
},
find_col_css: function() {
	var r, i;
	var rules;
	for (r = 0;r<document.styleSheets.length;r++) {
		rules = document.styleSheets[r].cssRules;
		for (i = 0;i<rules.length;i++) {
			if (3==rules[i].selectorText.length && rules[i].selectorText.substring(0, 2)==".c") {
				var n = rules[i].selectorText.charCodeAt(2) - 48;
				if (n>=0 && n<9) {
					gridder.col_css_rules[n] = rules[i];
				}
			}
		}
	}
},
set_grid: function(grid) {
	gridder.grid = grid;
	gridder.set_long(grid.grid);

	var info = document.getElementById("info");
	var out = "";
	var temp;

	try {
		temp = "";
		if (grid.is_easy()) {
			temp = gridder.strings.GetStringFromName("gridsidebar.easy");
		} else if (grid.is_medium()) {
			temp = gridder.strings.GetStringFromName("gridsidebar.medium");
		} else if (grid.is_hard()) {
			temp = gridder.strings.GetStringFromName("gridsidebar.hard");
		}
		if (""!=temp) {
			out+= "<tr><td>" + gridder.strings.GetStringFromName("difficulty.label") + "</td><th>" + temp + "(" + grid.get_rating() + ")</th></tr>"
		}
		temp = grid.get_best_time();
		if (""!=temp) {
			out+= "<tr><td>" + gridder.strings.GetStringFromName("besttime.label") + "</td><th>" + temp + "</th></tr>"
		}
		temp = grid.get_avg_time();
		if (""!=temp) {
			out+= "<tr><td>" + gridder.strings.GetStringFromName("avgtime.label") + "</td><th>" + temp + "</th></tr>"
		}
		if (grid.count>0) {
			if (1==grid.count) {
				out+= "<tr><td colspan='2'>" + gridder.strings.GetStringFromName("wincount1.label") + "</td></tr>"
			} else {
				out+= "<tr><td colspan='2'>" + gridder.strings.formatStringFromName("wincount.label", ["" + grid.count], 1) + "</td></tr>"
			}
			temp = grid.get_date();
			if (""!=temp) {
				out+= "<tr><td colspan='2'>" + gridder.strings.formatStringFromName("lastwin.label", [temp], 1) + "</td></tr>";
			}
		}
	} catch (e) { /*not exactly good practise*/
	}
	info.innerHTML = out;
},
clear_cell: function(r,c) {
	document.getElementById("cb" + r + c).className = "cb";
	var mn = document.getElementById("mn" + r + c)
	mn.innerHTML = "";
	mn.className = "mn";
	document.getElementById("hn" + r + c).innerHTML = "";
},
set_long: function(s) {
	var i = 0;
	for (var r = 0;r<9;r++) {
		for (var c = 0;c<s.length && c<9;c++) {
			gridder.clear_cell(r, c);
			gridder.cache[r][c] = 0;
			try {
				var n = s.charCodeAt(r*9+c) - 48;
				if (n>0 && n<=9) {
					gridder.set_hard(r, c, n);
				}
			} catch(e) {
			}
		}
		document.getElementById("s" + (r+1)).className = "";
	}
	gridder.start_timer();
},
start_timer: function() {
	gridder.timer_on = 1;
	gridder.ticks = 0;
},
stop_timer: function() {
	gridder.timer_on = 0;
},
watch: function() {
	if (gridder.timer_on) {
		gridder.ticks++;
		var el = document.getElementById("timer");
		if (gridder.ticks%60<10) {
			el.innerHTML = "" + Math.floor(gridder.ticks/60) + ":0" + (gridder.ticks%60);
		} else {
			el.innerHTML = "" + Math.floor(gridder.ticks/60) + ":" + (gridder.ticks%60);
		}
	}
},
set_hard: function(r, c, val) {
	var el = document.getElementById("cb" + r + c);
	el.className = el.className + " hard";
	document.getElementById("mn" + r + c).innerHTML = val;
	document.getElementById("hn" + r + c).innerHTML = "";
	gridder.cache[r][c] = -val;
},
hilight_column: function(column, hl) {
	if (null!=gridder.col_css_rules[column]) {
		gridder.col_css_rules[column].style.background = hl ? "#FFEECC" : null;
	}
},
cell_enter: function(ev) {
	var row = this.id.charCodeAt(0) - 48;
	var col = this.id.charCodeAt(1) - 48;
	if (-1!=gridder.hl_col && gridder.hl_col!=col) {
		gridder.hilight_column(gridder.hl_col, 0);
	}
	gridder.hilight_column(col, 1);
	gridder.hl_col = col;
},
cell_leave: function(ev) {
	if (-1==gridder.hl_col) {
		return ;
	}
	var row = this.id.charCodeAt(0) - 48;
	var col = this.id.charCodeAt(1) - 48;
	gridder.hilight_column(gridder.hl_col, 0);
	gridder.hl_col = -1;
},
cell_dblclick: function(ev) {
	var r = this.id.charCodeAt(0) - 48;
	var c = this.id.charCodeAt(1) - 48;
	if (gridder.cache[r][c]<0) {
		gridder.set_num(-gridder.cache[r][c]);
		return ;
	}
	var el2 = document.getElementById("mn" + this.id);
	el2.innerHTML = "";
	el2.className = "mn"
	gridder.cache[r][c] = 0;
	if (gridder.autocheck) {
		gridder.check_integrity();
	}
},
cell_click: function(ev) {
	ev.preventDefault();
	ev.stopPropagation();

	var el;
	var r = this.id.charCodeAt(0) - 48;
	var c = this.id.charCodeAt(1) - 48;
	if (gridder.cache[r][c]<0) {
		return ;
	}
	if (ev.ctrlKey || ev.button>0) {
		if (!gridder.num) {
			var el = document.getElementById("hn" + this.id);
			el.innerHTML = "";
			return ;
		}
		if (gridder.cache[r][c]>0) {
			return ;
		}
		var i;
		el = document.getElementById("hn" + this.id);
		var s = el.innerHTML;
		var map = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
		for (i = 0;i<s.length;i++) {
			if (s.charCodeAt(i)>=49 && s.charCodeAt(i)<=57) {
				map[s.charCodeAt(i)-48] = 1;
			}
		}
		map[gridder.num] = map[gridder.num] ? 0 : 2;
		s = "";
		var counter = 0;
		for (i = 1;i<=9;i++) {
			if(map[i]) {
				s+= "" + i;
				counter++;
				if (0==counter%3) {
					s+= "<br>";
				}
			}
		}
		el.innerHTML = s;
	} else {
		gridder.cache[r][c] = gridder.num;
		el = document.getElementById("mn" + this.id);
		if (!gridder.num) {
			el.innerHTML = "";
			el.className = "mn";
		} else {
			el.innerHTML = "" + gridder.num;
			el.className = "mn selmark";
		}
		if (gridder.autoclean_hint && 0!=gridder.num) {
			var el = document.getElementById("hn" + this.id);
			el.innerHTML = "";
		}
		if (gridder.autocheck) {
			gridder.check_integrity();
		}
	}
},
alert_cell: function(r, c, state) {
	if (-1==document.getElementById("cb" + r + c).className.indexOf("hard")) {
		document.getElementById("mn" + r + c).style.color = state ? "red" : null;
	}
},
check_integrity: function() {
	// FIXME: perhaps we should only check those that have changed?
	// Or perhaps not as we need this function anyway and it seems
	// to be fast enough.
	var temp;
	var r, c, i, sr, sc;
	var hasc, hasr;
	var counts =   [0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
	var alert_count = 0;
	var empty_cells = 0;
	var alerts = [];
	// columns and row
	for (r = 0;r<9;r++) {
		hasc = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
		hasr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
		hass = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
		for (c = 0;c<9;c++) {
			counts[Math.abs(gridder.cache[r][c])]++;
			if (0==gridder.cache[r][c])	empty_cells++;
			hasc[Math.abs(gridder.cache[r][c])]++;
			hasr[Math.abs(gridder.cache[c][r])]++;
			hass[Math.abs(gridder.cache[3*Math.floor(r/3)+Math.floor(c/3)][3*(r%3)+c%3])]++;
		}
		for (i = 1;i<10;i++) {
			if (hasc[i]>1) {
				for (c = 0;c<9;c++) {
					if (gridder.cache[r][c]==i) {
						alerts[r*10+c] = 1;
					}
				}
			}
			if (hasr[i]>1) {
				for (c = 0;c<9;c++) {
					if (gridder.cache[c][r]==i) {
						alerts[c*10+r] = 1;
					}
				}
			}
			if (hass[i]>1) {
				for (c = 0;c<9;c++) {
					var tr = 3*Math.floor(r/3)+Math.floor(c/3);
					var tc = 3*(r%3)+c%3;
					if (gridder.cache[tr][tc]==i) {
						alerts[tr*10+tc] = 1;
					}
				}
			}
		}
	}
	// colorize
	for (r = 0;r<9;r++) {
		for (c = 0;c<9;c++) {
			if ((r*10+c) in alerts) {
				gridder.alert_cell(r, c, 1);
				alert_count++;
			} else {
				gridder.alert_cell(r, c, 0);
			}
		}
	}
	if (gridder.autochecknum) {
		for (i = 1;i<10;i++) {
			var id = "s" + i;
			var sp = document.getElementById(id);
			if (gridder.num==i) {
				sp.className = "selmark";
			}
			if (counts[i]>=9) {
				sp.className+= " usedup";
			}
		}
	}
	if (0==empty_cells && 0==alert_count) {
		gridder.stop_timer();
		griddb.add_score(gridder.grid, gridder.ticks);
		gridder.finished();
	}
},
finished: function() {
	var el = document.getElementById("completed");
	el.style.visibility = "visible";
	gridder.ask_for_rating();
	
},
ask_for_rating: function() {
	if (gridder.prefs.getIntPref("rated")) {
		return ;
	}
	var browser = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
	   .getInterface(Components.interfaces.nsIWebNavigation)
	   .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
	   .rootTreeItem
	   .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
	   .getInterface(Components.interfaces.nsIDOMWindow).gBrowser;

	var notificationBox = browser.getNotificationBox(browser.getBrowserForDocument(document));
	// Remove existing notifications. Notifications get removed
	// automatically onclick and on page navigation, but we need to remove
	// them ourselves in the case of reload, or they stack up.
	for (var i = 0, child; child = notificationBox.childNodes[i]; i++) {
		if (child.getAttribute("value") == "grid-rate") {
			notificationBox.removeNotification(child);
		}
	}
	notificationBox.appendNotification(
		this.strings.GetStringFromName('rate.please'),
		"grid-rate",
		"",	// icon
		notificationBox.PRIORITY_WARNING_MEDIUM,
		[
		{ label: this.strings.GetStringFromName('rate.now'),
		 accessKey: "R",
		 popup: null,
		 callback: function(){ 
			gridder.prefs.setIntPref("rated", 1);
			document.location.href = "https://addons.mozilla.org/en-US/firefox/addon/8036";
		} },
		{ label: this.strings.GetStringFromName('rate.later'),
		 accessKey: "L",
		 popup: null,
		 callback: function(){ } },
		{ label: this.strings.GetStringFromName('rate.never'),
		 accessKey: "N",
		 popup: null,
		 callback: function(){ 
			gridder.prefs.setIntPref("rated", 1);
		} }
	]);
},
_x_sentinel: 0
}

